package com.qdlc.p2p.common.util;

import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * 身份证编号的JavaBean，包含解析和验证功能。
 * <p/>
 * 参考：
 * 《中华人民共和国行政区划》
 * http://www.china.com.cn/ch-quhua/daima.htm
 * 《GB 11643-1999 公民身份号码》
 * http://www.docin.com/p-67221285.html
 * <p/>
 * 注意：身份证号的最后一个如果是'X'，则一定要大写，不能是小写。
 */
public class IdCard {

    /** 第一代身份证 */
    public static final int ID_NUM_V1 = 15;

    /** 第二代身份证 */
    public static final int ID_NUM_V2 = 18;

    /** 行政区号（Administrative Region Number） */
    private String admRegNo = null;

    /** 出生日期 */
    private String birthday = null;

    /** 序列号 */
    private String seq = null;

    /** 校验码 */
    private String checkCode = null;

    /** 身份证号类型 */
    private int idType = ID_NUM_V2;

    /** 权值 */
    private static final int[] W = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7,
            9, 10, 5, 8, 4, 2, 1};
    /** ∑(ai×Wi) % 11 后对应的校验和 */
    private static final char[] A1 = new char[]{'1', '0', 'X', '9', '8', '7',
            '6', '5', '4', '3', '2'};

    /**
     * 返回字符数组。
     * <p/>
     * 注意：不做任何检查。只有是第二代身份证的时候，结果中才包含校验码。
     *
     * @return 字符数组
     */
    private char[] toCharArray() {
        StringBuilder buf = new StringBuilder();
        buf.append(this.admRegNo);
        buf.append(this.birthday);
        buf.append(this.seq);
        if (this.idType == IdCard.ID_NUM_V2) {
            buf.append(this.checkCode);
        }
        return buf.toString().toCharArray();
    }

    /**
     * 检验身份证号是否正确。
     *
     * @param idNo 身份证号
     * @return 格式是否正确
     */
    public static IdCard getInstance(String idNo) {
        IdCard idNum = parseIdNum(idNo);
        if (idNum != null && idNum.isValid()) {
            return idNum;
        }
        throw new IllegalArgumentException("身份证无效!");
    }

    /**
     * 检查该身份证号信息是否正确。
     *
     * @return 是否正确
     */
    private boolean isValid() {
        if (this.idType != ID_NUM_V1 && this.idType != ID_NUM_V2) {
            return false;
        }
        // 检查行政区号
        if (StringUtils.isBlank(this.admRegNo) || this.admRegNo.length() != 6
                || !StringUtils.isNumeric(this.admRegNo)) {
            return false;
        }
        // 检查出生日期
        if (StringUtils.isBlank(this.birthday)
                || !StringUtils.isNumeric(this.birthday)) {
            return false;
        } else {
            SimpleDateFormat sdf = null;
            if (this.idType == ID_NUM_V1) {
                if (this.birthday.length() != 6) {
                    return false;
                }
                sdf = new SimpleDateFormat("yyMMdd");
                sdf.setLenient(false);
            } else {
                if (this.birthday.length() != 8) {
                    return false;
                }
                sdf = new SimpleDateFormat("yyyyMMdd");
                sdf.setLenient(false);
            }
            try {
                sdf.parse(this.birthday);
            } catch (ParseException e) {
                return false;
            }
        }

        // 检查顺序码
        if (StringUtils.isBlank(this.seq) || this.seq.length() != 3
                || !StringUtils.isNumeric(this.seq)) {
            return false;
        }

        // 检查校验和校验码
        if (this.idType == ID_NUM_V2) {
            if (StringUtils.isBlank(this.checkCode)
                    || this.checkCode.length() != 1
                    || !(CharUtils.isAsciiNumeric(this.checkCode.charAt(0)) || this.checkCode.charAt(0) == 'X')) {
                return false;
            }
            char[] charArr = this.toCharArray();
            char code = calcCheckCode(charArr);
            if (code != charArr[17]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 计算第二代身份证号的校验码。
     *
     * @param idNo 第二代身份证号的字符数组。
     *             要求长度必须为18位，但最后一位可以是任意值。
     * @return 校验码
     */
    private static char calcCheckCode(char[] idNo) {
        if (idNo == null || idNo.length != 18) {
            throw new IllegalArgumentException("身份证号的长度必须为18位。");
        }
        for (int i = 0; i < 17; i++) {
            char ch = idNo[i];
            if (!CharUtils.isAsciiNumeric(ch)) {
                throw new IllegalArgumentException("身份证号的的前17位必须是数字。");
            }
        }
        int a, w;
        int sum = 0;
        for (int i = 0; i < 17; i++) {
            a = idNo[i] - 0x30; // 字符 -> 数值
            w = W[i];
            sum += a * w;
        }
        return A1[sum % 11];
    }

    /**
     * 解析身份证号码。
     * <p/>
     * 注意：不做任何验证，仅仅分段截取。
     *
     * @param idNo 身份证号码的字符串形式。
     * @return 新创建的IdNum对象。如果参数idNo的长度不为15或18, 均返回null。
     */
    private static IdCard parseIdNum(String idNo) {
        if (idNo == null || StringUtils.isBlank(idNo)) {
            return null;
        }
        int len = idNo.length();
        if (len == 15) {
            IdCard idNum = new IdCard();
            idNum.setIdType(ID_NUM_V1);
            idNum.setAdmRegNo(idNo.substring(0, 6));
            idNum.setBirthday(idNo.substring(6, 12));
            idNum.setSeq(idNo.substring(12, 15));
            return idNum;
        } else if (len == 18) {
            IdCard idNum = new IdCard();
            idNum.setIdType(ID_NUM_V2);
            idNum.setAdmRegNo(idNo.substring(0, 6));
            idNum.setBirthday(idNo.substring(6, 14));
            idNum.setSeq(idNo.substring(14, 17));
            idNum.setCheckCode(idNo.substring(17, 18));
            return idNum;
        }
        return null;
    }

    /**
     * 获取行政区号。
     *
     * @return 行政区号
     */
    public String getAdmRegNo() {
        return admRegNo;
    }

    /**
     * 设置行政区号。
     *
     * @param admRegNo 行政区号
     */
    private void setAdmRegNo(String admRegNo) {
        this.admRegNo = admRegNo;
    }

    /**
     * 获取出生日期。
     *
     * @return 出生日期
     */
    public String getBirthday() {
        return birthday;
    }

    /**
     * 设置出生日期。
     *
     * @param birthday 出生日期
     */
    private void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    /**
     * 获取序列号。
     *
     * @return 序列号
     */
    public String getSeq() {
        return seq;
    }

    /**
     * 设置序列号。
     *
     * @param seq 序列号
     */
    private void setSeq(String seq) {
        this.seq = seq;
    }

    /**
     * 获取校验码。
     *
     * @return 校验码
     */
    public String getCheckCode() {
        return checkCode;
    }

    /**
     * 设置校验码。
     *
     * @param checkCode 校验码
     */
    private void setCheckCode(String checkCode) {
        this.checkCode = checkCode;
    }

    /**
     * 获取身份证号类型。
     *
     * @return 身份证号类型
     */
    public int getIdType() {
        return idType;
    }

    /**
     * 设置身份证号类型。
     *
     * @param idType 身份证号类型
     */
    private void setIdType(int idType) {
        this.idType = idType;
    }

    public static void main(String[] args) {
        IdCard idCard = IdCard.getInstance("332623198012030818");
        if (idCard != null) {
            System.out.print(idCard.getBirthday());
        } else {
            System.out.print("无效身份证");
        }
    }
}
